<!DOCTYPE html>
<html lang='zh-CN'>

<head>
  <meta name="generator" content="Hexo 6.3.0">
  <meta name="hexo-theme" content="https://github.com/xaoxuu/hexo-theme-stellar/tree/1.19.0">
  <meta charset="utf-8">
  

  <meta http-equiv='x-dns-prefetch-control' content='on' />
  <link rel='dns-prefetch' href='https://gcore.jsdelivr.net'>
  <link rel="preconnect" href="https://gcore.jsdelivr.net" crossorigin>
  <link rel='dns-prefetch' href='//unpkg.com'>

  <meta name="renderer" content="webkit">
  <meta name="force-rendering" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="HandheldFriendly" content="True" >
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta name="theme-color" content="#f8f8f8">
  
  <title>Redis核心技术与原理 - 愔颂</title>

  
    <meta name="description" content="Redis核心知识介绍。包括持久化、事务、过期策略、内存淘汰机制、主从复制、哨兵机制、集群部署、缓存和数据库一致性问题。">
<meta property="og:type" content="article">
<meta property="og:title" content="Redis核心技术与原理">
<meta property="og:url" content="https://farhills.gitee.io/2023/10/05/Redis%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF%E4%B8%8E%E5%8E%9F%E7%90%86/index.html">
<meta property="og:site_name" content="愔颂">
<meta property="og:description" content="Redis核心知识介绍。包括持久化、事务、过期策略、内存淘汰机制、主从复制、哨兵机制、集群部署、缓存和数据库一致性问题。">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://cdn.nlark.com/yuque/0/2023/png/36098302/1696040313457-edda8f46-f68c-4fad-bc98-ed4c7d0cfe1d.png">
<meta property="og:image" content="https://cdn.nlark.com/yuque/0/2023/png/36098302/1696040404744-140c6973-6929-4871-b323-aa0701b6187e.png">
<meta property="og:image" content="https://cdn.nlark.com/yuque/0/2023/jpeg/36098302/1696056042075-cb57047f-da72-4596-8a3e-cc4c1cd483dc.jpeg">
<meta property="og:image" content="https://cdn.nlark.com/yuque/0/2023/jpeg/36098302/1696057478116-91748f1c-0ff5-48a4-a121-a028cb7b06cd.jpeg">
<meta property="og:image" content="https://cdn.nlark.com/yuque/0/2023/jpeg/36098302/1696058499629-09449fbb-3f61-48a8-b30c-aade5c37972a.jpeg">
<meta property="og:image" content="https://cdn.nlark.com/yuque/0/2023/jpeg/36098302/1696065563782-40edd6e8-81c0-4065-b791-bd3098d5a064.jpeg">
<meta property="article:published_time" content="2023-10-05T03:13:35.018Z">
<meta property="article:modified_time" content="2023-10-04T03:49:52.205Z">
<meta property="article:author" content="远岫">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://cdn.nlark.com/yuque/0/2023/png/36098302/1696040313457-edda8f46-f68c-4fad-bc98-ed4c7d0cfe1d.png">
  
  
  
  

  <!-- feed -->
  

  
    
<link rel="stylesheet" href="/css/main.css">

  

  
    <link rel="shortcut icon" href="https://z1.ax1x.com/2023/10/05/pPXijyT.png">
  

  

  


  
</head>

<body>
  




  <div class='l_body' id='start'>
    <aside class='l_left' layout='post'>
    

  

<header class="header"><div class="logo-wrap"><a class="avatar" href="/about/"><div class="bg" style="opacity:0;background-image:url(https://gcore.jsdelivr.net/gh/cdn-x/placeholder@1.0.4/avatar/round/rainbow64@3x.webp);"></div><img no-lazy class="avatar" src="https://s1.ax1x.com/2022/11/12/ziJjfK.jpg" onerror="javascript:this.classList.add('error');this.src='https://gcore.jsdelivr.net/gh/cdn-x/placeholder@1.0.4/image/2659360.svg';"></a><a class="title" href="/"><div class="main" ff="title">愔颂</div></a></div>

<nav class="menu dis-select"><a class="nav-item active" href="/">文章</a><a class="nav-item" href="/friends/">收藏</a><a class="nav-item" href="/about/">关于</a></nav>
</header>


<div class="widgets">
<widget class="widget-wrapper search"><div class="widget-body"><div class="search-wrapper" id="search"><form class="search-form"><input type="text" class="search-input" id="search-input" data-filter="/blog/" placeholder="文章搜索"><svg t="1670596976048" class="icon search-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2676" width="200" height="200"><path d="M938.2 832.6L723.8 618.1c-2.5-2.5-5.3-4.4-7.9-6.4 36.2-55.6 57.3-121.8 57.3-193.1C773.3 222.8 614.6 64 418.7 64S64 222.8 64 418.6c0 195.9 158.8 354.6 354.6 354.6 71.3 0 137.5-21.2 193.2-57.4 2 2.7 3.9 5.4 6.3 7.8L832.5 938c14.6 14.6 33.7 21.9 52.8 21.9 19.1 0 38.2-7.3 52.8-21.8 29.2-29.1 29.2-76.4 0.1-105.5M418.7 661.3C284.9 661.3 176 552.4 176 418.6 176 284.9 284.9 176 418.7 176c133.8 0 242.6 108.9 242.6 242.7 0 133.7-108.9 242.6-242.6 242.6" p-id="2677"></path></svg></form><div id="search-result"></div><div class="search-no-result">没有找到内容！</div></div></div></widget>


<widget class="widget-wrapper toc single" id="data-toc"><div class="widget-header cap dis-select"><span class="name">Redis核心技术与原理</span></div><div class="widget-body fs14"><div class="doc-tree active"><ol class="toc"><li class="toc-item toc-level-3"><a class="toc-link" href="#1%E3%80%81Redis%E6%98%AF%E5%A6%82%E4%BD%95%E6%89%A7%E8%A1%8C%E7%9A%84%EF%BC%9F"><span class="toc-text">1、Redis是如何执行的？</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2%E3%80%81Redis%E6%8C%81%E4%B9%85%E5%8C%96"><span class="toc-text">2、Redis持久化</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#2-1%E3%80%81RDB"><span class="toc-text">2.1、RDB</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-2%E3%80%81AOF"><span class="toc-text">2.2、AOF</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-3%E3%80%81%E6%B7%B7%E5%90%88%E6%8C%81%E4%B9%85%E5%8C%96"><span class="toc-text">2.3、混合持久化</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3%E3%80%81Redis%E4%BA%8B%E5%8A%A1"><span class="toc-text">3、Redis事务</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4%E3%80%81Redis%E8%BF%87%E6%9C%9F%E7%AD%96%E7%95%A5"><span class="toc-text">4、Redis过期策略</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5%E3%80%81Redis%E7%AE%A1%E9%81%93%E6%8A%80%E6%9C%AF"><span class="toc-text">5、Redis管道技术</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#6%E3%80%81%E5%86%85%E5%AD%98%E6%B7%98%E6%B1%B0%E6%9C%BA%E5%88%B6"><span class="toc-text">6、内存淘汰机制</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7%E3%80%81%E4%B8%BB%E4%BB%8E%E5%BA%93%E6%80%8E%E4%B9%88%E5%AE%9E%E7%8E%B0%E6%95%B0%E6%8D%AE%E4%B8%80%E8%87%B4%EF%BC%9F"><span class="toc-text">7、主从库怎么实现数据一致？</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#8%E3%80%81%E5%93%A8%E5%85%B5%E6%9C%BA%E5%88%B6"><span class="toc-text">8、哨兵机制</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#9%E3%80%81Redis%E5%88%87%E7%89%87%E9%9B%86%E7%BE%A4"><span class="toc-text">9、Redis切片集群</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#10%E3%80%81%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E7%BC%93%E5%AD%98%E5%92%8C%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E6%95%B0%E6%8D%AE%E4%B8%80%E8%87%B4%E6%80%A7%E9%97%AE%E9%A2%98%EF%BC%9F"><span class="toc-text">10、如何解决缓存和数据库的数据一致性问题？</span></a></li></ol></div></div></widget>




</div>


    </aside>
    <div class='l_main'>
      

      



<div class="bread-nav fs12"><div id="breadcrumb"><a class="cap breadcrumb" href="/">主页</a><span class="sep"></span><a class="cap breadcrumb" href="/">文章</a><span class="sep"></span><a class="cap breadcrumb-link" href="/categories/Redis/">Redis</a></div><div id="post-meta">发布于&nbsp;<time datetime="2023-10-05T03:13:35.018Z">2023-10-05</time></div></div>

<article class='md-text content post'>
<h1 class="article-title"><span>Redis核心技术与原理</span></h1>
<meta name="referrer" content="no-referrer"/>

<p>Redis核心知识介绍。包括持久化、事务、过期策略、内存淘汰机制、主从复制、哨兵机制、集群部署、缓存和数据库一致性问题。</p>
<span id="more"></span>

<h3 id="1、Redis是如何执行的？"><a href="#1、Redis是如何执行的？" class="headerlink" title="1、Redis是如何执行的？"></a>1、Redis是如何执行的？</h3><p>一条命令的执行过程有很多细节，但大体可分为：</p>
<ul>
<li>客户端先将用户输入的命令，转化为 Redis 相关的通讯协议，再用 socket 连接的方式将内容发送给服务器端。</li>
<li>服务器端在接收到相关内容之后，先将内容转化为具体的执行命令，再判断用户授权信息和其他相关信息，当验证通过之后会执行命令。</li>
<li>命令执行完之后，会进行相关的信息记录和数据统计，然后再把执行结果发送给客户端，这样一条命令的执行流程就结束了。</li>
<li>如果是集群模式的话，主节点还会将命令同步至子节点。</li>
</ul>
<h3 id="2、Redis持久化"><a href="#2、Redis持久化" class="headerlink" title="2、Redis持久化"></a>2、Redis持久化</h3><p>Redis 的读写都是在内存中，所以它的性能较高，但在内存中的数据会随着服务器的重启而丢失，为了保证数据不丢失，我们需要将内存中的数据存储到磁盘，以便 Redis 重启时能够从磁盘中恢复原有的数据，而整个过程就叫做 Redis 持久化。</p>
<p>Redis 持久化拥有以下三种方式：</p>
<ul>
<li><strong>快照方式</strong>（RDB, Redis DataBase）将某一个时刻的内存数据，以二进制的方式写入磁盘；</li>
<li><strong>文件追加方式</strong>（AOF, Append Only File），记录所有的操作命令，并以文本的形式追加到文件中；</li>
<li><strong>混合持久化方式</strong>，Redis 4.0 之后新增的方式，混合持久化是结合了 RDB 和 AOF 的优点，在写入的时候，先把当前的数据以 RDB 的形式写入文件的开头，再将后续的操作命令以 AOF 的格式存入文件，这样既能保证 Redis 重启时的速度，又能减低数据丢失的风险。</li>
</ul>
<h4 id="2-1、RDB"><a href="#2-1、RDB" class="headerlink" title="2.1、RDB"></a>2.1、RDB</h4><p>RDB（Redis DataBase）是将某一个时刻的内存快照（Snapshot），以二进制的方式写入磁盘的过程。</p>
<p><strong>持久化触发</strong>：</p>
<p><code>1）手动触发</code></p>
<p>手动触发持久化的操作有两个： save 和 bgsave ，它们主要区别体现在：是否阻塞 Redis 主线程的执行。</p>
<p><code>2）自动触发</code></p>
<ol>
<li>save m n</li>
</ol>
<p>save m n 是指在 m 秒内，如果有 n 个键发生改变，则自动触发持久化。</p>
<ol start="2">
<li>flushall</li>
</ol>
<p>flushall 命令用于清空 Redis 数据库，在生产环境下一定慎用，当 Redis 执行了 flushall 命令之后，则会触发自动持久化，把 RDB 文件清空。 </p>
<ol start="3">
<li>主从同步触发</li>
</ol>
<p>在 Redis 主从复制中，当从节点执行全量复制操作时，主节点会执行 bgsave 命令，并将 RDB 文件发送给从节点，该过程会自动触发 Redis 持久化。</p>
<p><strong>RDB文件恢复：</strong></p>
<p>当 Redis 服务器启动时，如果 Redis 根目录存在 RDB 文件 dump.rdb，Redis 就会自动加载 RDB 文件恢复持久化数据。 </p>
<p><strong>优点：</strong></p>
<ul>
<li>RDB 的内容为二进制的数据，占用内存更小，更紧凑，更适合做为备份文件；</li>
<li>RDB 对灾难恢复非常有用，它是一个紧凑的文件，可以更快的传输到远程服务器进行 Redis 服务恢复；</li>
<li>RDB 可以更大程度的提高 Redis 的运行速度，因为每次持久化时 Redis 主进程都会 fork() 一个子进程，进行数据持久化到磁盘，Redis 主进程并不会执行磁盘 I&#x2F;O 等操作；</li>
<li>与 AOF 格式的文件相比，RDB 文件可以更快的重启。</li>
</ul>
<p><strong>缺点：</strong></p>
<ul>
<li>因为 RDB 只能保存某个时间间隔的数据，如果中途 Redis 服务被意外终止了，则会丢失一段时间内的 Redis 数据；</li>
<li>RDB 需要经常 fork() 才能使用子进程将其持久化在磁盘上。如果数据集很大，fork() 可能很耗时，并且如果数据集很大且 CPU 性能不佳，则可能导致 Redis 停止为客户端服务几毫秒甚至一秒钟。</li>
</ul>
<h4 id="2-2、AOF"><a href="#2-2、AOF" class="headerlink" title="2.2、AOF"></a>2.2、AOF</h4><p>AOF（Append Only File）中文是附加到文件，顾名思义 AOF 可以把 Redis 每个键值对操作都记录到文件（appendonly.aof）中。</p>
<p>注：使用 RDB 持久化有一个风险，它可能会造成最新数据丢失的风险。因为 RDB 的持久化有一定的时间间隔，在这个时间段内如果 Redis 服务意外终止（断电，系统崩溃、Redis进程死亡）的话，就会造成最新的数据全部丢失。</p>
<p><strong>持久化触发：</strong></p>
<p><code>1）自动触发</code></p>
<ul>
<li>always：每条 Redis 操作命令都会写入磁盘，最多丢失一条数据；</li>
<li>everysec：每秒钟写入一次磁盘，最多丢失一秒的数据；</li>
<li>no：不设置写入磁盘的规则，根据当前操作系统来决定何时写入磁盘，Linux 默认 30s 写入一次数据至磁盘。</li>
</ul>
<p><code>2）手动触发</code></p>
<p>在客户端执行 bgrewriteaof 命令就可以手动触发 AOF 持久化。</p>
<p><strong>AOF重写：</strong></p>
<p>AOF 是通过记录 Redis 的执行命令来持久化（保存）数据的，所以随着时间的流逝 AOF 文件会越来越多，这样不仅增加了服务器的存储压力，也会造成 Redis 重启速度变慢，为了解决这个问题 Redis 提供了 AOF 重写的功能。</p>
<p><strong>AOF 重写流程</strong></p>
<p>AOF 文件重写是生成一个全新的文件，并把当前数据的最少操作命令保存到新文件上，当把所有的数据都保存至新文件之后，Redis 会交换两个文件，并把最新的持久化操作命令追加到新文件上。</p>
<p><strong>数据恢复：</strong></p>
<p>正常情况下，只要开启了 AOF 持久化，并且提供了正常的 appendonly.aof 文件，在 Redis 启动时就会自定加载 AOF 文件并启动。</p>
<p><strong>持久化文件加载规则</strong></p>
<ul>
<li>如果只开启了 AOF 持久化，Redis 启动时只会加载 AOF 文件（appendonly.aof），进行数据恢复；</li>
<li>如果只开启了 RDB 持久化，Redis 启动时只会加载 RDB 文件（dump.rdb），进行数据恢复；</li>
<li>如果同时开启了 RDB 和 AOF 持久化，Redis 启动时只会加载 AOF 文件（appendonly.aof），进行数据恢复。</li>
<li>在 AOF 开启的情况下，即使 AOF 文件不存在，则创建一个空的 appendonly.aof 文件，并基于这个空的 appendonly.aof 文件启动。</li>
</ul>
<h4 id="2-3、混合持久化"><a href="#2-3、混合持久化" class="headerlink" title="2.3、混合持久化"></a>2.3、混合持久化</h4><p>在开启混合持久化的情况下，AOF 重写时会把 Redis 的持久化数据，以 RDB 的格式写入到 AOF 文件的开头，之后的数据再以 AOF 的格式化追加的文件的末尾。</p>
<div class="tag-plugin image"><div class="image-bg"><img src="https://cdn.nlark.com/yuque/0/2023/png/36098302/1696040313457-edda8f46-f68c-4fad-bc98-ed4c7d0cfe1d.png" fancybox="true"/></div></div>

<p>混合持久化的加载规则：</p>
<div class="tag-plugin image"><div class="image-bg"><img src="https://cdn.nlark.com/yuque/0/2023/png/36098302/1696040404744-140c6973-6929-4871-b323-aa0701b6187e.png" fancybox="true"/></div></div>



<h3 id="3、Redis事务"><a href="#3、Redis事务" class="headerlink" title="3、Redis事务"></a>3、Redis事务</h3><p>Redis 中的事务从开始到结束也是要经历三个阶段：</p>
<ul>
<li>开启事务</li>
<li>命令入列</li>
<li>执行事务&#x2F;放弃事务</li>
</ul>
<p>其中，开启事务使用 multi 命令，事务执行使用 exec 命令，放弃事务使用 discard 命令。</p>
<p>Redis不支持事务回滚的原因有以下两个：</p>
<ul>
<li>认为 Redis 事务的执行时，错误通常都是编程错误造成的，这种错误通常只会出现在开发环境中，而很少会在实际的生产环境中出现，所以他认为没有必要为 Redis 开发事务回滚功能；</li>
<li>不支持事务回滚是因为这种复杂的功能和 Redis 追求的简单高效的设计主旨不符合。</li>
</ul>
<p>这里不支持事务回滚，指的是不支持运行时错误的事务回滚。</p>
<p><strong>监控</strong></p>
<p>watch 命令用于客户端并发情况下，为事务提供一个乐观锁（CAS，Check And Set），也就是可以用 watch 命令来监控一个或多个变量，如果在事务的过程中，某个监控项被修改了，那么整个事务就会终止执行。</p>
<h3 id="4、Redis过期策略"><a href="#4、Redis过期策略" class="headerlink" title="4、Redis过期策略"></a>4、Redis过期策略</h3><p>常见的过期策略有以下三种：</p>
<ul>
<li>定时删除</li>
<li>惰性删除</li>
<li>定期删除</li>
</ul>
<p><strong>定时删除</strong></p>
<p>在设置键值过期时间时，创建一个定时事件，当过期时间到达时，由事件处理器自动执行键的删除操作。</p>
<ul>
<li><strong>优点：</strong>保证内存可以被尽快地释放。</li>
<li><strong>缺点：</strong>在 Redis 高负载的情况下或有大量过期键需要同时处理时，会造成 Redis 服务器卡顿，影响主业务执行。</li>
</ul>
<p><strong>惰性删除</strong></p>
<p>不主动删除过期键，每次从数据库获取键值时判断是否过期，如果过期则删除键值，并返回 null。</p>
<ul>
<li><strong>优点：</strong>因为每次访问时，才会判断过期键，所以此策略只会使用很少的系统资源。</li>
<li><strong>缺点：</strong>系统占用空间删除不及时，导致空间利用率降低，造成了一定的空间浪费。</li>
</ul>
<p><strong>定期删除</strong></p>
<p>每隔一段时间检查一次数据库，随机删除一些过期键。</p>
<p>Redis 默认每秒进行 10 次过期扫描，此配置可通过 Redis 的配置文件 redis.conf 进行配置，配置键为 hz 它的默认值是 hz 10。</p>
<p>注：</p>
<p>1、Redis 每次扫描并不是遍历过期字典中的所有键，而是随机抽取判断并删除过期键的形式执行的。</p>
<p>2、过期键在主从模式下，从库对过期键的处理要完全依靠主库，主库删除过期键之后会发送 del 命令给所有的从库。</p>
<h3 id="5、Redis管道技术"><a href="#5、Redis管道技术" class="headerlink" title="5、Redis管道技术"></a>5、Redis管道技术</h3><p>管道技术（Pipeline）是客户端提供的一种批处理技术，用于一次处理多个 Redis 命令，从而提高整个交互的性能。</p>
<p>通常情况下 Redis 是单行执行的，客户端先向服务器发送请求，服务端接收并处理请求后再把结果返回给客户端，这种处理模式在非频繁请求时不会有任何问题。</p>
<p>但如果出现集中大批量的请求时，因为每个请求都要经历先请求再响应的过程，这就会造成网络资源浪费，此时就需要管道技术来把所有的命令整合一次发给服务端，再一次响应给客户端，这样就能大大的提升了 Redis 的响应速度。</p>
<h3 id="6、内存淘汰机制"><a href="#6、内存淘汰机制" class="headerlink" title="6、内存淘汰机制"></a>6、内存淘汰机制</h3><p>早期版本的 Redis 有以下 6 种淘汰策略：</p>
<ol>
<li><strong>noeviction</strong>：不淘汰任何数据，当内存不足时，新增操作会报错，Redis 默认内存淘汰策略；</li>
<li><strong>allkeys-lru</strong>：淘汰整个键值中最久未使用的键值；</li>
<li><strong>allkeys-random</strong>：随机淘汰任意键值;</li>
<li><strong>volatile-lru</strong>：淘汰所有设置了过期时间的键值中最久未使用的键值；</li>
<li><strong>volatile-random</strong>：随机淘汰设置了过期时间的任意键值；</li>
<li><strong>volatile-ttl</strong>：优先淘汰更早过期的键值。</li>
</ol>
<p>在 Redis 4.0 版本中又新增了 2 种淘汰策略：</p>
<ol>
<li><strong>volatile-lfu</strong>：淘汰所有设置了过期时间的键值中，最少使用的键值；</li>
<li><strong>allkeys-lfu</strong>：淘汰整个键值中最少使用的键值。</li>
</ol>
<p>其中 allkeys-xxx 表示从所有的键值中淘汰数据，而 volatile-xxx 表示从设置了过期键的键值中淘汰数据。</p>
<h3 id="7、主从库怎么实现数据一致？"><a href="#7、主从库怎么实现数据一致？" class="headerlink" title="7、主从库怎么实现数据一致？"></a>7、主从库怎么实现数据一致？</h3><p>Redis 提供了主从库模式，以保证数据副本的一致，主从库之间采用的是读写分离的方式。</p>
<ul>
<li><strong>读操作</strong>：主库、从库都可以接收；</li>
<li><strong>写操作</strong>：首先到主库执行，然后，主库将写操作同步给从库。</li>
</ul>
<p><strong>主从库如何进行第一次同步？</strong></p>
<p>1、当我们启动多个 Redis 实例的时候，它们相互之间就可以通过 replicaof（Redis 5.0 之前使用 slaveof）命令形成主库和从库的关系，之后会按照三个阶段完成数据的第一次同步。</p>
<div class="tag-plugin image"><div class="image-bg"><img src="https://cdn.nlark.com/yuque/0/2023/jpeg/36098302/1696056042075-cb57047f-da72-4596-8a3e-cc4c1cd483dc.jpeg" fancybox="true"/></div></div>

<p>第一阶段是主从库间建立连接、协商同步的过程，主要是为全量复制做准备。</p>
<p>具体来说，从库给主库发送 psync 命令，表示要进行数据同步，主库根据这个命令的参数来启动复制。psync 命令包含了主库的 runID 和复制进度 offset 两个参数。</p>
<ul>
<li>runID，是每个 Redis 实例启动时都会自动生成的一个随机 ID，用来唯一标记这个实例。当从库和主库第一次复制时，因为不知道主库的 runID，所以将 runID 设为“？”。</li>
<li>offset，此时设为 -1，表示第一次复制。</li>
</ul>
<p>主库收到 psync 命令后，会用 FULLRESYNC 响应命令（全量复制）带上两个参数：主库 runID 和主库目前的复制进度 offset，返回给从库。</p>
<p>在第二阶段，主库将所有数据同步给从库。从库收到数据后，在本地完成数据加载。这个过程依赖于内存快照生成的 RDB 文件。</p>
<p>在主库将数据同步给从库的过程中，主库不会被阻塞，仍然可以正常接收请求。否则，Redis 的服务就被中断了。但是，这些请求中的写操作并没有记录到刚刚生成的 RDB 文件中。为了保证主从库的数据一致性，主库会在内存中用专门的 replication buffer，记录 RDB 文件生成后收到的所有写操作。</p>
<p>最后，也就是第三个阶段，主库会把第二阶段执行过程中新收到的写命令，再发送给从库。具体的操作是，当主库完成 RDB 文件发送后，就会把此时 replication buffer 中的修改操作发给从库，从库再重新执行这些操作。这样一来，主从库就实现同步了。</p>
<p>主从级联模式：“主 - 从 - 从”模式中，为了减轻主库压力，手动选择一个从库（比如选择内存资源配置较高的从库），用于级联其他的从库。建立起主从关系：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">replicaof  所选从库的IP 6379</span><br></pre></td></tr></table></figure>

<p>这样一来，这些从库就会知道，在进行同步时，不用再和主库进行交互了，只要和级联的从库进行写操作同步就行了，这就可以减轻主库上的压力。</p>
<p><strong>主从库之间网络断了怎么办？</strong></p>
<p>网络断了之后，主从库会采用增量复制的方式继续同步。即只会把主从库网络断连期间主库收到的命令，同步给从库。</p>
<h3 id="8、哨兵机制"><a href="#8、哨兵机制" class="headerlink" title="8、哨兵机制"></a>8、哨兵机制</h3><div class="tag-plugin image"><div class="image-bg"><img src="https://cdn.nlark.com/yuque/0/2023/jpeg/36098302/1696057478116-91748f1c-0ff5-48a4-a121-a028cb7b06cd.jpeg" fancybox="true"/></div></div>



<p><strong>主观下线</strong>：</p>
<p>哨兵进程会使用 PING 命令检测它自己和主、从库的网络连接情况，用来判断实例的状态。如果哨兵发现主库或从库对 PING 命令的响应超时了，那么，哨兵就会先把它标记为“主观下线”。</p>
<p><strong>客观下线：</strong></p>
<p>为了避免单个哨兵因为自身网络状况不好，而误判主库下线的情况，引入多个哨兵，被称为哨兵集群。</p>
<p>当大多数的哨兵实例，都判断主库已经“主观下线”了，主库就会被标记为“客观下线”</p>
<p><strong>如何选定新主库？</strong></p>
<p>三个规则：从库优先级、从库复制进度以及从库 ID 号。只要在某一轮中，有从库得分最高，那么它就是主库了，选主过程到此结束。如果没有出现得分最高的从库，那么就继续进行下一轮。</p>
<p>第一轮：优先级最高的从库得分高。</p>
<p>第二轮：和旧主库同步程度最接近的从库得分高。</p>
<p>第三轮：ID 号小的从库得分高。</p>
<h3 id="9、Redis切片集群"><a href="#9、Redis切片集群" class="headerlink" title="9、Redis切片集群"></a>9、Redis切片集群</h3><p><strong>如何保存更多数据？</strong></p>
<p>为了保存大量数据，有大内存云主机和切片集群两种方法。实际上，这两种方法分别对应着 Redis 应对数据量增多的两种方案：纵向扩展（scale up）和横向扩展（scale out）。</p>
<ul>
<li><strong>纵向扩展</strong>：升级单个 Redis 实例的资源配置，包括增加内存容量、增加磁盘容量、使用更高配置的 CPU。</li>
<li><strong>横向扩展</strong>：横向增加当前 Redis 实例的个数，</li>
</ul>
<div class="tag-plugin image"><div class="image-bg"><img src="https://cdn.nlark.com/yuque/0/2023/jpeg/36098302/1696058499629-09449fbb-3f61-48a8-b30c-aade5c37972a.jpeg" fancybox="true"/></div></div>



<p><strong>数据和实例之间如何对应呢？</strong></p>
<p>在切片集群中，数据需要分布在不同实例上，那么，数据和实例之间如何对应呢？</p>
<p>从 3.0 开始，官方提供了一个名为 Redis Cluster 的方案，用于实现切片集群。Redis Cluster 方案中就规定了数据和实例的对应规则。</p>
<p>具体来说，Redis Cluster 方案采用哈希槽（Hash Slot，接下来我会直接称之为 Slot），来处理数据和实例之间的映射关系。<mark>在 Redis Cluster 方案中，一个切片集群共有 16384 个哈希槽，这些哈希槽类似于数据分区，每个键值对都会根据它的 key，被映射到一个哈希槽中<mark>。</p>
<p>那么，这些哈希槽又是如何被映射到具体的 Redis 实例上的呢？</p>
<p>我们在部署 Redis Cluster 方案时，可以<mark>使用 cluster create 命令创建集群，此时，Redis 会自动把这些槽平均分布在集群实例上<mark>。例如，如果集群中有 N 个实例，那么，每个实例上的槽个数为 16384&#x2F;N 个。</p>
<p>当然， 我们也可以使用 cluster meet 命令手动建立实例间的连接，形成集群，再使用 cluster addslots 命令，指定每个实例上的哈希槽个数。</p>
<p><strong>客户端如何定位数据？</strong></p>
<p>在定位键值对数据时，它所处的哈希槽是可以通过计算得到的，这个计算可以在客户端发送请求时来执行。但是，要进一步定位到实例，还需要知道哈希槽分布在哪个实例上。</p>
<p>一般来说，客户端和集群实例建立连接后，实例就会把哈希槽的分配信息发给客户端。但是，在集群刚刚创建的时候，每个实例只知道自己被分配了哪些哈希槽，是不知道其他实例拥有的哈希槽信息的。</p>
<p>那么，客户端为什么可以在访问任何一个实例时，都能获得所有的哈希槽信息呢？这是因为，<mark>Redis 实例会把自己的哈希槽信息发给和它相连接的其它实例，来完成哈希槽分配信息的扩散。<mark>当实例之间相互连接后，每个实例就有所有哈希槽的映射关系了。</p>
<p><mark>客户端收到哈希槽信息后，会把哈希槽信息缓存在本地。当客户端请求键值对时，会先计算键所对应的哈希槽，然后就可以给相应的实例发送请求了。<mark></p>
<p>但是，在集群中，实例和哈希槽的对应关系并不是一成不变的，最常见的变化有两个：</p>
<ul>
<li>在集群中，实例有新增或删除，Redis 需要重新分配哈希槽；</li>
<li>为了负载均衡，Redis 需要把哈希槽在所有实例上重新分布一遍。</li>
</ul>
<p>情况一：当客户端把一个键值对的操作请求发给一个实例时，如果这个实例上并没有这个键值对映射的哈希槽。</p>
<p>那么，这个实例就会给客户端返回 MOVED 命令响应结果，这个结果中就包含了新实例的访问地址。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">GET hello:key</span><br><span class="line">(error) MOVED 13320 172.16.19.5:6379</span><br></pre></td></tr></table></figure>

<p>其中，MOVED 命令表示，客户端请求的键值对所在的哈希槽 13320，实际是在 172.16.19.5 这个实例上。通过返回的 MOVED 命令，就相当于把哈希槽所在的新实例的信息告诉给客户端了。这样一来，客户端就可以直接和 172.16.19.5 连接，并发送操作请求了。</p>
<p>情况二：客户端向实例 2 发送请求，但此时，Slot 2 中的数据只有一部分迁移到了实例 3，还有部分数据没有迁移。</p>
<p>在这种迁移部分完成的情况下，客户端就会收到一条 ASK 报错信息，如下所示：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">GET hello:key</span><br><span class="line">(error) ASK 13320 172.16.19.5:6379</span><br></pre></td></tr></table></figure>

<p>这个结果中的 ASK 命令就表示，客户端请求的键值对所在的哈希槽 13320，在 172.16.19.5 这个实例上，但是这个哈希槽正在迁移。此时，客户端需要先给 172.16.19.5 这个实例发送一个 ASKING 命令。这个命令的意思是，让这个实例允许执行客户端接下来发送的命令。然后，客户端再向这个实例发送 GET 命令，以读取数据。</p>
<p>比较：ASK 命令的作用只是让客户端能给新实例发送一次请求，而不像 MOVED 命令那样，会更改本地缓存，让后续所有命令都发往新实例。</p>
<h3 id="10、如何解决缓存和数据库的数据一致性问题？"><a href="#10、如何解决缓存和数据库的数据一致性问题？" class="headerlink" title="10、如何解决缓存和数据库的数据一致性问题？"></a>10、如何解决缓存和数据库的数据一致性问题？</h3><p>缓存和数据库不一致的问题，我们可以分成读写缓存和只读缓存两种情况进行分析。</p>
<div class="tag-plugin image"><div class="image-bg"><img src="https://cdn.nlark.com/yuque/0/2023/jpeg/36098302/1696065563782-40edd6e8-81c0-4065-b791-bd3098d5a064.jpeg" fancybox="true"/></div></div>



<p>为了解决Redis缓存数据一致性问题，可以采用以下方案：</p>
<p>缓存失效：在更新数据库数据时，同时使缓存失效。这种方式可以保证数据的最终一致性，但可能会导致一些短暂的数据不一致情况。</p>
<p>缓存更新：在更新数据库数据时，同时更新缓存。这种方式可以保证数据的强一致性，但可能会增加系统的复杂性和负载。</p>
<p>异步更新：在更新数据库数据时，采用异步方式更新缓存。这种方式可以保证系统的可用性和性能，但可能会导致一些短暂的数据不一致情况。</p>
<p>最佳实践：</p>
<p>合理选择缓存失效的时间：根据业务需求和系统特点，选择适当的缓存失效时间，以保证数据的及时性和一致性。</p>
<p>使用分布式锁：在进行数据库和缓存的更新操作时，使用分布式锁来避免并发问题导致的数据不一致情况。</p>
<p>定期同步缓存和数据库：定期进行缓存和数据库的同步操作，以保证数据的一致性。</p>
<p>采用合理的缓存策略：根据业务需求和系统特点，采用合理的缓存策略，如缓存失效、缓存更新、异步更新等，以保证数据的一致性和性能。</p>
<p>使用分布式事务：在进行数据库和缓存的更新操作时，使用分布式事务来保证数据的一致性和可靠性。</p>
<p>监控和告警：对Redis缓存系统进行监控和告警，及时发现和解决数据一致性问题。</p>
<p>————————————————</p>





</article>

<div class="related-wrap reveal" id="read-next"><section class="body"><div class="item" id="prev"><div class="note">较新文章</div><a href="/2023/10/05/Redis%E5%AE%9E%E7%8E%B0%E6%8E%92%E8%A1%8C%E6%A6%9C/">Redis实现排行榜</a></div><div class="item" id="next"><div class="note">较早文章</div><a href="/2023/10/05/NodeJs&Vue%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AE%E6%95%99%E7%A8%8B/">NodeJs&Vue安装配置教程</a></div></section></div>






  <div class='related-wrap md-text reveal' id="comments">
    <section class='header cmt-title cap theme'>
      快来参与讨论吧
    </section>
    <section class='body cmt-body giscus'>
      

<svg class="loading" style="vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2709"><path d="M832 512c0-176-144-320-320-320V128c211.2 0 384 172.8 384 384h-64zM192 512c0 176 144 320 320 320v64C300.8 896 128 723.2 128 512h64z" p-id="2710"></path></svg>

<div id="giscus" data-repo="echoalways/giscus" data-repo-id="R_kgDOKjJRiQ" data-category="Announcements" data-category-id="DIC_kwDOKjJRic4CaUxu" data-mapping="pathname" data-strict="0" data-reactions-enabled="1" data-emit-metadata="0" data-input-position="top" data-theme="preferred_color_scheme" data-lang="zh-CN" data-loading="lazy" crossorigin="anonymous"></div>

    </section>
  </div>



      
<footer class="page-footer reveal fs12"><hr><div class="text" style="text-align:center;"><p>云无心以出岫@远岫♥</p>
<div><span id="timeDate">载入天数...</span><span id="times">载入时分秒...</span>，<span id="busuanzi_container_site_pv">总访问量: <span id="busuanzi_value_site_pv"></span>次</span>，<span id="busuanzi_container_site_uv">访客数: <span id="busuanzi_value_site_uv"></span>人</span></div></div></footer>

<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
<script>
  function createtime() {
    var now = new Date();
    var grt= new Date("10/03/2023 18:40:00");
    now.setTime(now.getTime()+250);
    days = (now - grt ) / 1000 / 60 / 60 / 24; dnum = Math.floor(days);
    hours = (now - grt ) / 1000 / 60 / 60 - (24 * dnum); hnum = Math.floor(hours);
    if(String(hnum).length ==1 ){hnum = "0" + hnum;} minutes = (now - grt ) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum);
    mnum = Math.floor(minutes); if(String(mnum).length ==1 ){mnum = "0" + mnum;}
    seconds = (now - grt ) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);
    snum = Math.round(seconds); if(String(snum).length ==1 ){snum = "0" + snum;}
    document.getElementById("timeDate").innerHTML = "本站已运行 "+dnum+" 天 ";
    document.getElementById("times").innerHTML = hnum + " 小时 " + mnum + " 分 " + snum + " 秒";
  };
  setInterval("createtime()",250);
</script>

      <div class='float-panel mobile-only blur' style='display:none'>
  <button type='button' class='sidebar-toggle mobile' onclick='sidebar.toggle()'>
    <svg class="icon" style="width: 1em; height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15301"><path d="M566.407 808.3c26.9-0.1 49.3-20.8 51.6-47.6-1.9-27.7-23.9-49.7-51.6-51.6h-412.6c-28.2-1.4-52.6 19.5-55.5 47.6 2.3 26.8 24.6 47.5 51.6 47.6h416.5v4z m309.3-249.9c26.9-0.1 49.3-20.8 51.6-47.6-2.2-26.8-24.6-47.5-51.6-47.6h-721.9c-27.7-2.8-52.5 17.4-55.3 45.1-0.1 0.8-0.1 1.7-0.2 2.5 0.9 27.2 23.6 48.5 50.7 47.6H875.707z m-103.1-245.9c26.9-0.1 49.3-20.8 51.6-47.6-0.4-28.3-23.2-51.1-51.5-51.6h-618.9c-29.5-1.1-54.3 21.9-55.5 51.4v0.2c1.4 27.8 25.2 49.2 53 47.8 0.8 0 1.7-0.1 2.5-0.2h618.8z" p-id="15302"></path><path d="M566.407 808.3c26.9-0.1 49.3-20.8 51.6-47.6-1.9-27.7-23.9-49.7-51.6-51.6h-412.6c-28.2-1.4-52.6 19.5-55.5 47.6 1.9 27.7 23.9 49.7 51.6 51.6h416.5z m309.3-249.9c26.9-0.1 49.3-20.8 51.6-47.6-2.2-26.8-24.6-47.5-51.6-47.6h-721.9c-27.7-2.8-52.5 17.4-55.3 45.1-0.1 0.8-0.1 1.7-0.2 2.5 0.9 27.2 23.6 48.5 50.7 47.6H875.707z m-103.1-245.9c26.9-0.1 49.3-20.8 51.6-47.6-0.4-28.3-23.2-51.1-51.5-51.6h-618.9c-29.5-1.1-54.3 21.9-55.5 51.4v0.2c1.4 27.8 25.2 49.2 53 47.8 0.8 0 1.7-0.1 2.5-0.2h618.8z" p-id="15303"></path></svg>
  </button>
</div>

    </div>
  </div>
  <div class='scripts'>
    <script type="text/javascript">
  const stellar = {
    // 懒加载 css https://github.com/filamentgroup/loadCSS
    loadCSS: (href, before, media, attributes) => {
      var doc = window.document;
      var ss = doc.createElement("link");
      var ref;
      if (before) {
        ref = before;
      } else {
        var refs = (doc.body || doc.getElementsByTagName("head")[0]).childNodes;
        ref = refs[refs.length - 1];
      }
      var sheets = doc.styleSheets;
      if (attributes) {
        for (var attributeName in attributes) {
          if (attributes.hasOwnProperty(attributeName)) {
            ss.setAttribute(attributeName, attributes[attributeName]);
          }
        }
      }
      ss.rel = "stylesheet";
      ss.href = href;
      ss.media = "only x";
      function ready(cb) {
        if (doc.body) {
          return cb();
        }
        setTimeout(function () {
          ready(cb);
        });
      }
      ready(function () {
        ref.parentNode.insertBefore(ss, before ? ref : ref.nextSibling);
      });
      var onloadcssdefined = function (cb) {
        var resolvedHref = ss.href;
        var i = sheets.length;
        while (i--) {
          if (sheets[i].href === resolvedHref) {
            return cb();
          }
        }
        setTimeout(function () {
          onloadcssdefined(cb);
        });
      };
      function loadCB() {
        if (ss.addEventListener) {
          ss.removeEventListener("load", loadCB);
        }
        ss.media = media || "all";
      }
      if (ss.addEventListener) {
        ss.addEventListener("load", loadCB);
      }
      ss.onloadcssdefined = onloadcssdefined;
      onloadcssdefined(loadCB);
      return ss;
    },

    // 从 butterfly 和 volantis 获得灵感
    loadScript: (src, opt) => new Promise((resolve, reject) => {
      var script = document.createElement('script');
      if (src.startsWith('/')){
        src = stellar.config.root + src.substring(1);
      }
      script.src = src;
      if (opt) {
        for (let key of Object.keys(opt)) {
          script[key] = opt[key]
        }
      } else {
        // 默认异步，如果需要同步，第二个参数传入 {} 即可
        script.async = true
      }
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    }),

    // https://github.com/jerryc127/hexo-theme-butterfly
    jQuery: (fn) => {
      if (typeof jQuery === 'undefined') {
        stellar.loadScript(stellar.plugins.jQuery).then(fn)
      } else {
        fn()
      }
    }
  };
  stellar.version = '1.19.0';
  stellar.github = 'https://github.com/xaoxuu/hexo-theme-stellar/tree/1.19.0';
  stellar.config = {
    date_suffix: {
      just: '刚刚',
      min: '分钟前',
      hour: '小时前',
      day: '天前',
      month: '个月前',
    },
    root : '/',
  };

  // required plugins (only load if needs)
  stellar.plugins = {
    jQuery: 'https://gcore.jsdelivr.net/npm/jquery@3.6.2/dist/jquery.min.js'
  };

  if ('local_search') {
    stellar.search = {};
    stellar.search.service = 'local_search';
    if (stellar.search.service == 'local_search') {
      let service_obj = Object.assign({}, {"field":"all","path":"/search.json","content":true,"sort":"-date"});
      stellar.search[stellar.search.service] = service_obj;
    }
  }

  // stellar js
  stellar.plugins.stellar = Object.assign({"sites":"/js/plugins/sites.js","friends":"/js/plugins/friends.js","ghinfo":"/js/plugins/ghinfo.js","timeline":"/js/plugins/timeline.js","linkcard":"/js/plugins/linkcard.js","fcircle":"/js/plugins/fcircle.js","weibo":"/js/plugins/weibo.js"});

  stellar.plugins.marked = Object.assign("https://cdn.bootcdn.net/ajax/libs/marked/4.0.18/marked.min.js");
  // optional plugins
  if ('false' == 'true') {
    stellar.plugins.lazyload = Object.assign({"enable":false,"js":"https://gcore.jsdelivr.net/npm/vanilla-lazyload@17.8.3/dist/lazyload.min.js","transition":"blur"});
  }
  if ('true' == 'true') {
    stellar.plugins.swiper = Object.assign({"enable":true,"css":"https://unpkg.com/swiper@8.4.5/swiper-bundle.min.css","js":"https://unpkg.com/swiper@8.4.5/swiper-bundle.min.js"});
  }
  if ('' == 'true') {
    stellar.plugins.scrollreveal = Object.assign({"enable":null,"js":"https://gcore.jsdelivr.net/npm/scrollreveal@4.0.9/dist/scrollreveal.min.js","distance":"8px","duration":500,"interval":100,"scale":1});
  }
  if ('true' == 'true') {
    stellar.plugins.preload = Object.assign({"enable":true,"service":"flying_pages","instant_page":"https://gcore.jsdelivr.net/gh/volantis-x/cdn-volantis@4.1.2/js/instant_page.js","flying_pages":"https://gcore.jsdelivr.net/gh/gijo-varghese/flying-pages@2.1.2/flying-pages.min.js"});
  }
  if ('true' == 'true') {
    stellar.plugins.fancybox = Object.assign({"enable":true,"js":"https://gcore.jsdelivr.net/npm/@fancyapps/ui@4.0/dist/fancybox.umd.js","css":"https://gcore.jsdelivr.net/npm/@fancyapps/ui@4.0/dist/fancybox.css","selector":".swiper-slide img"});
  }
  if ('false' == 'true') {
    stellar.plugins.heti = Object.assign({"enable":false,"css":"https://unpkg.com/heti@0.9.2/umd/heti.min.css","js":"https://unpkg.com/heti@0.9.2/umd/heti-addon.min.js"});
  }
  if ('true' == 'true') {
    stellar.plugins.copycode = Object.assign({"enable":true,"js":"/js/plugins/copycode.js","default_text":"Copy","success_text":"Copied"});
  }
</script>

<!-- required -->

  
<script src="/js/main.js" async></script>



<!-- optional -->

  <script>
  function loadJS() {
    const els = document.querySelectorAll("#comments #giscus");
    if (els.length === 0) return;
    els.forEach((el, i) => {
      try {
        el.innerHTML = '';
      } catch (error) {
        console.log(error);
      }
      var script = document.createElement('script');
      script.src = 'https://giscus.app/client.js';
      script.async = true;
      for (let key of Object.keys(el.attributes)) {
        let attr = el.attributes[key];
        if (['class', 'id'].includes(attr.name) === false) {
          script.setAttribute(attr.name, attr.value);
        }
      }
      el.appendChild(script);
    });
  }
  window.addEventListener('DOMContentLoaded', (event) => {
    loadJS();
  });
</script>




<!-- inject -->


  </div>
</body>
</html>
